package com.raidan.dclog.core.render.html;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.ResourceBundle;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.raidan.dclog.core.Config;
import com.raidan.dclog.core.Const;
import com.raidan.dclog.core.FileUtils;
import com.raidan.dclog.core.Secundomer;
import com.raidan.dclog.core.Utils;
import com.raidan.dclog.core.render.IRender;
import com.raidan.dclog.core.render.queries.Query;
import com.raidan.dclog.core.render.queries.QueryLoader;

/**
 * Класс выполнения запросов и отображения их в виде HTML страниц
 * 
 * @author raidan
 * 
 */
public class HTMLRender implements IRender {

	private final static Logger logger = LoggerFactory.getLogger(HTMLRender.class);

	private String htmlTemplate;
	private String indexHtmlTemplate;

	private String preBody;
	private String preRowHead;
	private String th;
	private String postRowHead;
	private String preRow;
	private String td;
	private String postRow;
	private String postBody;
	private String linkPre;
	private String linkPost;

	private String indexPreRow;
	private String indexPostRow;

	private DecimalFormat formatter = new DecimalFormat("#,##0.0##");

	private String reportDir;
	private String defaultEncoding;

	public HTMLRender() throws IOException {

		this.reportDir = Config.getInstance().getReportDir();
		this.defaultEncoding = Config.getInstance().getDefaultEncoding();

		// Очищаем каталог с результатам работы
		File dir = new File(reportDir);
		FileUtils.removeDirectory(dir);
		dir.mkdirs();

		// Загружаем шаблоны
		htmlTemplate = FileUtils.loadFromJAR("com/raidan/dclog/core/render/html/html-template.properties").toString()
				.trim();

		indexHtmlTemplate = FileUtils.loadFromJAR("com/raidan/dclog/core/render/html/html-index-template.properties")
				.toString().trim();

		ResourceBundle bundle = ResourceBundle.getBundle("com.raidan.dclog.core.render.html.html-body-template");

		preBody = bundle.getString("preBody");
		preRowHead = bundle.getString("preRowHead");
		th = bundle.getString("th");
		postRowHead = bundle.getString("postRowHead");
		preRow = bundle.getString("preRow");
		td = bundle.getString("td");
		postRow = bundle.getString("postRow");
		postBody = bundle.getString("postBody");
		linkPre = bundle.getString("linkPre");
		linkPost = bundle.getString("linkPost");

		bundle = ResourceBundle.getBundle("com.raidan.dclog.core.render.html.html-index-body-template");

		indexPreRow = bundle.getString("preRow");
		indexPostRow = bundle.getString("preRow");

	}

	/**
	 * Рендерим запросы
	 * 
	 * @param connection
	 *            Connection
	 */
	public void render(Connection connection) throws SQLException, IOException {
		try {

			logger.debug("Loading statistics, using HTML render.");

			Map<String, Query> queries = QueryLoader.getInstance().getQueries();

			String version = Utils.getVersion();

			File file = new File(Config.getInstance().getReportDir() + "index.html");
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, false), Config
					.getInstance().getDefaultEncoding()));

			String firstPart = indexHtmlTemplate.substring(0, indexHtmlTemplate.indexOf("%body%"));
			String secondPart = indexHtmlTemplate.substring(indexHtmlTemplate.indexOf("%body%") + "%body%".length());
			firstPart = firstPart.replaceAll("%title%", "Gathered statistics");
			firstPart = firstPart.replaceAll("%version%", version);
			writer.write(firstPart);
			for (Query q : queries.values()) {
				writer.write(indexPreRow);
				writer.write("'" + q.getName() + ".html'>");
				writer.write(q.getDescription());
				writer.write(indexPostRow);
				writer.write("\n");
			}
			writer.write(secondPart);
			writer.close();

			Config.getInstance().getProgressInstance().processStart(true);
			int i = 0;
			for (Query q : queries.values()) {
				this.executeQueryAndSave(connection, q, version, "");
				Config.getInstance().getProgressInstance().process(i++, queries.size());
			}

		} catch (SQLException sql) {
			throw sql;
		} finally {
			Config.getInstance().getProgressInstance().processOver();
		}
	}

	/**
	 * Выполнение запроса и сохранение его в отдельный файл
	 * 
	 * @param connection
	 * @param fileName
	 * @param query
	 * @param description
	 * @throws SQLException
	 * @throws IOException
	 */
	private void executeQueryAndSave(Connection connection, Query query, String version, String parent)
			throws SQLException, IOException {

		File file = new File(reportDir + parent + Utils.safeFileName(query.getName()) + ".html");

		if (file.exists()) {
			logger.info("SKIP rendering report '{}'. It's already rendered.", query.getDescription());
			return; //
		}

		logger.info("Rendering report '{}'...", query.getDescription());

		StringBuilder firstPart = new StringBuilder(htmlTemplate.substring(0, htmlTemplate.indexOf("%body%")));
		StringBuilder secondPart = new StringBuilder(htmlTemplate.substring(htmlTemplate.indexOf("%body%")
				+ "%body%".length()));

		Utils.replaceAll(firstPart, "%title%", query.getDescription());
		Utils.replaceAll(firstPart, "%version%", version);
		Utils.replaceAll(firstPart, "%query%", query.getSql());

		Secundomer common = new Secundomer();
		Secundomer executeQuery = new Secundomer();

		Collection<String> cycleRows = null;
		int cycleColumn = 0;
		Query qTemplate = null;

		if (query.isCycle()) {

			logger.trace("Preloading cycle rules.");

			cycleRows = new ArrayList<String>();
			cycleColumn = Integer.parseInt(query.getCycleColumn());
			qTemplate = QueryLoader.getInstance().getCycleTemplates().get(query.getCycleTemplate());
			if (qTemplate == null) {
				throw new IllegalStateException("Unable to find linked cycle template '" + query.getCycleTemplate()
						+ "' for template " + query + ".");
			}

		}

		common.start();

		BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, false),
				this.defaultEncoding));
		try {
			writer.write(firstPart.toString()); //

			executeQuery.start();

			PreparedStatement statement = connection.prepareStatement(query.getSql());
			ResultSet rs = statement.executeQuery();
			executeQuery.stop();

			try {

				ResultSetMetaData mtd = rs.getMetaData();
				int columns = mtd.getColumnCount();

				// Печатаем заголовки
				writer.write(preBody);
				writer.write("\n" + preRowHead);

				int cycleAlowFlag = 0;
				int typeCols[] = new int[columns + 1];
				for (int i = 1; i <= columns; i++) {
					typeCols[i] = mtd.getColumnType(i);
					// if ("Timestamp".equals(mtd.getColumnName(i))) {
					// typeCols[i] = Types.TIMESTAMP;
					// }
					if ("cycle_flag".equals(mtd.getColumnName(i))) {
						cycleAlowFlag = i;
					} else {
						writer.write(th + mtd.getColumnName(i));
					}
				}
				writer.write(postRowHead + "\n");

				// Печатаем тело
				int rowCount = this.renderResult(writer, rs, columns, typeCols, cycleColumn, qTemplate, cycleRows,
						cycleAlowFlag);
				statement.close();

				writer.write(postBody);

				common.stop();
				String stats = "" + rowCount + " rows. Executed at: " + common.getTotalTime() + " msec (sql query for "
						+ executeQuery.getTotalTime() + " msec) \n";

				Utils.replaceAll(secondPart, "%stats%", stats);

				// Печатаем окончание документа
				writer.write(secondPart.toString());

				logger.debug(" .. " + stats.trim());
			} finally {
				// Не забаываем все аккуратно закрыть.
				rs.close();
				statement.close();
			}
		} catch (SQLException sql) {
			logger.error("Critical error during executing template " + query + " and query '" + query.getSql() + "'.");
			throw sql;

		} finally {
			writer.close();

			// В любом случае, нам тут нечего коммитить
			connection.rollback();

		}

		if (cycleColumn > 0) {
			Config.getInstance().getProgressInstance().processStart(true);
			int i = 0;
			try {
				new File(reportDir + qTemplate.getName() + "/").mkdirs();
				for (String item : cycleRows) {

					Query qInstance = qTemplate.clone();
					qInstance.setDescription(Utils.replaceAll(qInstance.getDescription(), Const.CYCLE_MACROS, item));
					qInstance.setSql(Utils.replaceAll(qInstance.getSql(), Const.CYCLE_MACROS, item
							.replaceAll("'", "''")));
					qInstance.setName(item);

					this.executeQueryAndSave(connection, qInstance, version, qTemplate.getName() + "/");

					Config.getInstance().getProgressInstance().process(i++, cycleRows.size());
				}
			} finally {
				Config.getInstance().getProgressInstance().processOver();
			}
		}
	}

	/**
	 * Построчная запись resultSet-а
	 * 
	 * @param writer
	 * @param rs
	 * @param columns
	 * @throws SQLException
	 * @throws IOException
	 * 
	 * @return numer of rows
	 */
	private int renderResult(BufferedWriter writer, ResultSet rs, int columns, int typeCols[], int cycleColumn,
			Query qTemplate, Collection<String> cycleRows, int cycleAlowFlag) throws SQLException, IOException {

		int rowCount = 0;
		while (rs.next()) {
			rowCount++;

			boolean allowCycle = true;
			if (cycleAlowFlag > 0) {
				allowCycle = rs.getBoolean(cycleAlowFlag);
			}

			writer.write(preRow);
			for (int i = 1; i <= columns; i++) {

				if (i == cycleAlowFlag) {
					continue; //
				}

				String columnText = null;
				if (typeCols[i] == Types.NUMERIC) {
					columnText = formatter.format((float) Math.round(rs.getFloat(i) * 1000) / 1000f);
				} else if (typeCols[i] == Types.TIMESTAMP) {
					columnText = "<nobr>" + rs.getTimestamp(i) + "</nobr>";
				} else {
					columnText = rs.getString(i);
				}
				writer.write(td);
				if (i == cycleColumn && allowCycle) {
					cycleRows.add(rs.getString(cycleColumn));
					writer.write(linkPre);
					writer.write("'" + qTemplate.getName() + "/" + Utils.safeFileName(columnText) + ".html'");
					writer.write(">");
					writer.write(columnText);
					writer.write(linkPost);
				} else {
					writer.write(columnText);
				}
			}
			writer.write(postRow + "\n");
		}

		return rowCount;

	}
}
